package com.flynn.rock.system.aspect;

import cn.hutool.json.JSONUtil;
import com.flynn.rock.annotation.OperateLog;
import com.flynn.rock.model.dto.OperateLogDto;
import com.flynn.rock.system.service.SysOperateLogService;
import com.flynn.rock.util.HttpServletUtil;
import com.flynn.rock.util.OperateLogUtil;
import com.flynn.rock.util.SecurityUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Map;
import java.util.Objects;

@Slf4j
@Aspect
@Component
public class OperateLogAspect {

    @Resource
    private SysOperateLogService sysOperateLogService;

    @Before("@annotation(operateLog)")
    public void operateLogBefore(OperateLog operateLog) {
        OperateLogUtil.start();
    }

    @AfterReturning(
            value = "@annotation(operateLog)",
            returning = "returnResult"
    )
    public void operateLogAfter(JoinPoint joinPoint, OperateLog operateLog, Object returnResult) {
        try {
            this.handleOperateLog(joinPoint, operateLog, returnResult, null);
        } catch (Exception e) {
            // do nothing
        }
    }

    @AfterThrowing(
            value = "@annotation(operateLog)",
            throwing = "exception"
    )
    public void operateLogAfterThrowing(JoinPoint joinPoint, OperateLog operateLog, Throwable exception) {
        try {
            this.handleOperateLog(joinPoint, operateLog, null, exception);
        } catch (Exception e) {
            // do nothing
        }
    }

    private void handleOperateLog(JoinPoint joinPoint, OperateLog operateLog,
                                  Object returnResult, Throwable exception) {
        HttpServletRequest request = HttpServletUtil.getRequest();
        OperateLogDto operateLogDto = new OperateLogDto();
        try {
            operateLogDto.setOperateTitle(operateLog.operateTitle());
            operateLogDto.setOperateType(operateLog.operateType());
            operateLogDto.setBusinessType(operateLog.businessType());
            operateLogDto.setOperateUserId(SecurityUtil.getCurrentUserId());
            operateLogDto.setOperateUserName(SecurityUtil.getCurrentUser().getUser().getRealName());
            operateLogDto.setOperateDeptId(SecurityUtil.getCurrentUserDeptId());
            operateLogDto.setConsumeTime(OperateLogUtil.end());
            String ip = HttpServletUtil.getIp(request);
            operateLogDto.setOperateIp(ip);
            operateLogDto.setOperateAddress(HttpServletUtil.getCityInfo(ip));
            operateLogDto.setRequestUrl(request.getRequestURI());
            operateLogDto.setRequestMethod(request.getMethod());
            this.setRequestValue(request, joinPoint, operateLogDto);
            this.setResponseValue(returnResult, exception, operateLogDto);
            sysOperateLogService.saveOperateLog(operateLogDto);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void setResponseValue(Object returnResult, Throwable exception, OperateLogDto operateLog) {
        if (Objects.nonNull(exception)) {
            operateLog.setStatus("ERROR");
            operateLog.setResponseParams(StringUtils.substring(exception.getMessage(), 0, 2000));
        } else {
            operateLog.setStatus("INFO");
            operateLog.setResponseParams(StringUtils.substring(JSONUtil.toJsonStr(returnResult), 0, 2000));
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param operateLog 操作日志
     */
    private void setRequestValue(HttpServletRequest servletRequest, JoinPoint joinPoint, OperateLogDto operateLog) {
        String requestMethod = servletRequest.getMethod();
        Map<String, String> requestParamMap = HttpServletUtil.getRequestParamMap(servletRequest);
        if (CollectionUtils.isEmpty(requestParamMap) &&
                (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) {
            String params = argsArrayToString(joinPoint);
            operateLog.setRequestParams(StringUtils.substring(params, 0, 2000));
        } else {
            operateLog.setRequestParams(StringUtils.substring(JSONUtil.toJsonStr(requestParamMap), 0, 2000));
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(JoinPoint joinPoint) {
        Object[] paramsArray = joinPoint.getArgs();
        StringBuilder params = new StringBuilder();
        if (paramsArray == null) {
            return params.toString();
        }
        for (Object o : paramsArray) {
            try {
                if (Objects.nonNull(o) && !OperateLogUtil.isFilterObject(o)) {
                    params.append(JSONUtil.toJsonStr(o)).append(" ");
                }
            } catch (Exception e) {
                // do nothing
            }
        }
        return params.toString().trim();
    }
}
